#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>


/*********************************************************************************************************
提指针就离不开内存
指针是用来寻找内存的
计算机（CPU）在处理数据的时候，需要的数据是在内存中读取的，处理后的内存也会放回内存中
那内存是如何管理的呢？
其实是把内存划分为一个个的内存单元，每个内存单元的大小取1个字节
1 Byte = 8 bit
1 KB = 1024 Byte
....
bit - 比特位
Byte - 字节
一个比特位可以存储一个2进制的1或0

每个内存单元，就是一个字节，里面能放八个比特位，每个内存单元都有一个编号
在计算机中，给内存单元的编号就称为地址，c语言中给地址起了新的名字就叫指针
so 内存单元的编号 = 地址 = 指针
*****************************************************************************************************************/

/*****************************************************************************************************************
 计算机内很多硬件单元，硬件单元是要互相协同工作的，至少要进行数据传输，用“线”连起来进行数据传输
 CPU和内存之间就有大量的数据交互，所以两只之间必须用线连起来
 两者之间有 控制总线（操作 R/W 读/写） 数据总线(移动数据) 地址总线（找到数据在哪）
 CPU访问内存中的某个字节空间，就必须知道这个字节空间在内存中的声明位置
 所以就需要给内存进行编制
 注：计算机中的编制，并不是把每个字节的地址记录下来，而是通过硬件设施完成的（即，只是存在地址，并未储存）
 简单理解：32位及其就有32根地址总线，每根线都有两态，表示0/1，则32根地址总线，就能表示2^32种含义，每个含义都代表一个地址
 地址信息被下达给内存，在内存中，就可以找到对应地址的数据，将数据通过数据总线传入CPU内寄存器
******************************************************************************************************************/


//
//int main()
//{
//	int a = 10;//创建变量其实就是向内存申请空间
//	&a;//取出a的地址
//	printf("%p\n", &a);//&a取出的是a所占的4个字节中地址较小的字节的地址
//	return 0;
//}

/************************************************************************************************************
int * p = &a p是一个变量（指针变量）是一块空间
空间内存的编号 = 地址 = 指针
存放地址的变量 = 指针变量
存放在指针变量中的值都会被理解为指针（在锤子眼里，什么都是钉子）
int * p = &a
上面语句，p左边写的 int *， * 是在说明p是指针变量，而前面的int是在说明p指向的是整形类型的对象

c语言中，只要拿到了地址（指针），就可以通过地址（指针）找到地址（指针）指向的对象，用解引用操作符（*），是一类间接访问操作符
*************************************************************************************************************/

//int main()
//{
//	int a = 0;
//	int* p = &a;
//	*p = 10;
//	printf("%d\n", a);//10
//	return 0;
//}

/************************************************************************************************************
指针变量的大小：
32位机器（x86）假设有32根地址总线，也就是有32个0/1序列，把32个0/1序列当作一个地址，那么一个地址就是32个bit位，需要4个字节才能存储
指针变量是用来存储地址的，所以32位机器的指针变量的大小是4个字节的空间
同理，64位机器（x64）的指针变量的大小就是8个字节的空间
指针变量的大小取决于地址的大小
也就是指针变量的大小是和类型无关的，只取决于操作机器位的大小
******************************************************************************************************************/

//int main()
//{
//	printf("%zd\n", sizeof(char*));
//	printf("%zd\n", sizeof(int*));
//	printf("%zd\n", sizeof(short*));
//	printf("%zd\n", sizeof(float*));
//	printf("%zd\n", sizeof(double*));
//	return 0;
//}

//那指针变量的类型有什么意义呢？？

//int main()
//{
//	int n = 0x11223344;
//	int* p = &n;
//	*p = 0; //将n的4个字节全部改为0
//	return 0;
//}

//int main()
//{
//	int n = 0x11223344;
//	char* p = &n;
//	*p = 0;//只能将第一个字节改为0
//	return 0;
//}

//总结：指针的类型决定了，对指针解引用的时候有多大的权限（即是一次能够操作几个字节）

//int main()
//{
//	int n = 10;
//	char* pc = &n;
//	int* pi = &n;
//
//	printf("&n =       = %p\n", &n);
//	printf("char* pc   = %p\n", pc);
//	printf("int* pi    = %p\n", pi);
//	printf("char* pc+1 = %p\n", pc + 1);
//	printf("int* pi    = %p\n", pi + 1);
//	return;
//}

//看以看出，char * 类型的指针变量+1跳过1个字节（一个字符的大小），int * 类型的指针变量+1跳过4个字节（一个整形的大小）
//这就是指针变量的类型差异带来的变化
//结论：指针变量的类型，决定了指针变量向前或者向后走一步有多大（距离）

/*
* 指针类型有一种特殊的类型是void * 类型，可以理解为无具体类型的指针（或者叫泛型指针），这种类型的指针变量可以用来接收任意类型地址
* 但也有局限性，void * 类型的指针变量不能直接进行指针的+-整数和解引用的运算
*/

//int main()
//{
//	int a = 0;
//	float f = 0.0f;
//	int* p = &a;
//	//char* pa = &a;//warning:不兼容
//	void* pa = &a;//可以
//	pa = &f;
//	return 0;
//}

//int main()
//{
//	int a = 10;
//	void* p = &a;
//
//	*p = 20;//err
//	p = p + 1;//err
//	return 0;
//}

//const修饰

//int main()
//{
//	const int a = 0;
//
//	a = 10;//err
//
//	int b = 0;
//	b = 20;//right
//	return 0;
//}

//int main()
//{
//	const int a = 0;
//
//	int* p = &a;
//	*p = 20;//明明被const修饰了，结果使用a的地址还是修改了a，打破了规则
//	printf("%d\n", a);
//	return 0;
//}

/*
* 变量是可以修改的，但如果把变量的地址交给一个指针变量，通过指针变量也是可以修改这个变量的
* 给变量加一个限制，使得其无法被修改
* const int a 使得a具有了常属性（无法被修改）但a的本质还是变量，成为常变量
* 注：C++中，const修饰的常量就本质上转化为常量了
*/

//int main()
//{
//	int a = 10;
//	int b = 20;
//
//	int* const p = &a;
//	//p = &b;//err
//	*p = b;
//	printf("%d\n", a);
//}

//const修饰指针变量本身的时候，放在*的右边
//此时，指针变量就不能再指向其他变量了
//但是可以通过指针变量，修改指针变量指向的内容

//
//int main()
//{
//	int a = 10;
//	int b = 20;
//
//	int const * p = &a;
//	printf("%p\n", p);
//	//*p = b;//err
//	p = &b;
//	printf("%p\n", p);
//
//	return 0;
//}

//const放在*左边的时候，限制的是指针变量的内容，不能通过指针变量修改指针变量指向的内容
//但是可以修改指针变量本身的值（也就是指针变量的指向）

//*左右两侧都可以加const，那样将严格限制变化

//int main()
//{
//	int a = 10;
//	int b = 20;
//
//	int const* const p = &a;
//	p = &b;//err
//	*p = b;//err
//	return 0;
//}

//指针的运算
// 1.指针 +- 整数
// 2.指针 - 指针
// 3.指针的关系运算
//


//一：指针 +- 整数
//1.用数组下标的方式打印数组的内容
//int main()
//{
//	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//	//打印数组的内容
//	int i = 0;
//	int sz = sizeof(arr) / sizeof(arr[0]);
//	for (i = 0; i < sz; i++)
//	{
//		printf("%d ", arr[i]);
//	}
//	return 0;
//}


//2.用指针的方式
//int main()
//{
//	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//	int* p = &arr[0];
//	int i = 0;
//	int sz = sizeof(arr) / sizeof(arr[0]);
//	for (i = 0; i < sz; i++)
//	{
//		printf("%d ",*(p + i));
//	}
//	return 0;
//}

//int main()
//{
//	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//	int* p = &arr[0];
//	int i = 0;
//	int sz = sizeof(arr) / sizeof(arr[0]);
//	for (i = 0; i < sz; i++)
//	{
//		printf("%d ", *p);
//		p++;
//	}
//	return 0;
//}

//二 指针 - 指针
// 指针 - 指针的结果就是两个指针变量之间的元素的个数
//指针 - 指针的前提条件一定是：两个指针指向了同一块空间
//int main()
//{
//	int arr[10] = { 0 };
//	printf("%d\n", &arr[9] - &arr[0]);
//	return 0;
//}
//int main()
//{
//	int arr[10] = { 0 };
//	char ch[5] = { 0 };
//
//	printf("%d\n", &ch[4] - &arr[2]);//err
//
//	return 0;
//}
#include <string.h>

//int main()
//{
//	//strlen - 求字符串长度，strlen统计的是字符串中\0之前的字符的个数
//	char arr[] = "abcdef";
//	//[a b c d e f \0]
//
//	int len = strlen(arr);
//	printf("%d\n", len);
//
//	return 0;
//}

//int my_strlen(char* str)
//{
//	int count = 0;
//
//	while (*str != '\0')
//	{
//		str++;
//		count++;
//	}
//	return count;
//}
//

//int my_strlen(char* str)
//{
//	char* start = str;
//	while (*str != '\0')
//	{
//		str++;
//	}
//	return str - start;
//	//指针 - 指针
//}
//
//
//int main()
//{
//	//strlen - 求字符串长度，strlen统计的是字符串中\0之前的字符的个数
//	char arr[] = "abcdef";
//	//[a b c d e f \0]
//
//	int len = my_strlen(arr);//数组名arr是数组首元素的地址 arr == &arr[0]
//
//	printf("%d\n", len);
//
//	return 0;
//}

////三 指针的关系运算
//int main()
//{
//	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
//	int sz = sizeof(arr) / sizeof(arr[0]);
//
//	int* p = arr;//arr == &arr[0]
//
//	while (p < arr + sz) //指针的大小比较
//	{
//		printf("%d ", *p);
//		p++;
//	}
//	return 0;
//}

/***********************************************************
* 野指针
**********************************************************/

//int main()
//{
//	int a = 10;
//	int* p = &a;
//	*p = 20;
//	printf("%d", *p);
//	return 0;
//}

//int main()
//{
//	int* p;//局部变量的指针未初始化，默认为随机值
//	*p = 20;
//	printf("%d\n", *p);//err
//	return 0;
//}

//int main()
//{
//	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//	int* p = arr;//此时p并不是野指针
//
//	int i = 0;
//	int sz = sizeof(arr) / sizeof(arr[0]);
//	for (i = 0; i <= sz + 1; i++)//当指针指向的范围超出数组的范围的时候，p就是野指针
//	{
//		printf("%d ", *p);
//		p++;
//	}
//	return 0;
//}

//指针指向的空间释放
//int* test()
//{
//	int a = 10;
//	return &a;
//}
////该代码看似没问题，实则有问题，离开函数test之后，a的空间已经还给了操作系统
////此时回到主函数的指针变量p就是一个野指针了
//int main()
//{
//	int* p = test();
//	printf("%d\n", *p);
//	return 0;
//}


//如何规避野指针
//1.指针初始化

//int main()
//{
//	int a = 0;
//
//	int* p = &a;
//	return  0;
//}
//int main()
//{
//	int* p = NULL;//拿绳子，把野狗，拴在树上
//
//	*p = 100;//err
//
//	printf("%d\n", *p);
//	return 0;
//}
//2.小心指针的越界
//int main()
//{
//	int arr[10] = { 0 };
//
//	int* p = arr;
//	int i = 0;
//
//	for (i = 0; i <= 20; i++)
//	{
//		*(p + i) = 2;//err
//		printf("%d ", *(p + i));
//	}
//}
//3.指针变量不再使用时候，及时置为NULL,指针使用前检查有效性
//注意：即使野狗被拴起来，我们也不要去挑逗野狗，有点危险
//在使用之前，要确认是不是野狗
//
//int main()
//{
//	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//	int* p = &arr[0];
//	int i = 0;
//	for (i = 0; i < 10; i++)
//	{
//		*(p++) = i;
//	}
//	//此时p已经越界了，可以把p置为NULL
//	p = NULL;
//	//下次使用的时候判断一下p是不是NULL再使用
//
//	p = &arr[3];
//	if (p != NULL)
//	{
//		//使用
//	}
//	return 0;
//}

//避免返回局部变量的地址

//int* test()
//{
//	int a = 10;
//	int* p = &a;
//	return p;
//}
//
//
//int main()
//{
//	int* p = test();//err
//	printf("%d\n", *p);
//	return 0;
//}

//assert断言
//需要包含头文件
//#define NDEBUG
//
//#include <assert.h>
//
//int main()
//{
//	int* p = NULL;
//	assert(p != NULL);
//	*p = 20;
//	printf("%d\n", *p);
//
//	return 0;
//}
//定义一个宏NDEBUG指令，就取消assert断言
//assert()的缺点是，引入了额外的检查，增加了程序的运行时间
//一般在Debug中使用，在Release版本选择禁用assert中即可
//像vs这种集成开发环境，一般在RElease版本中，会直接将assert()优化掉

//strlen的模拟实现
#include <assert.h>


//size_t my_strlen(const char* str)
//{
//	assert(str != NULL);
//
//	size_t count = 0;
//
//	while (*str != '\0')
//	{
//		str++;
//		count++;
//	}
//	return count;
//}
//int main()
//{
//	char arr[] = "abcdefg";
//	size_t len = my_strlen(arr);
//	printf("%d\n", len);
//	return 0;
//}

//写一个函数，实现交换两个整数的内容之
//传值调用，和传址调用
//学习指针的目的是使用指针解决问题，那什么问题，非指针不可呢

//void Swap(int x, int y)
//{
//	int temp = 0;
//	temp = x;
//	x = y;
//	y = temp;
//}
//
//
//
//int main()
//{
//	int a = 10;
//	int b = 20;
//	printf("交换前 a = %d b = %d\n", a, b);
//	Swap(a, b);
//	printf("交换后 a = %d b = %d\n", a, b);
//
//	return 0;
//}
//
// 传值调用
////该代码并未实现功能，为什么呢？？？
////实参传递给形参的时候，形参会单独创建一份临时空间来接收实参，对形参的修改是不修改实参的值的
////形参只是实参的一份临时拷贝




//传址调用（指针就派上用场啦！！！）

void Swap(int* x, int* y)
{
	int temp = 0;
	temp = *x;
	*x = *y;
	*y = temp;
}


int main()
{
	int a = 10;
	int b = 20;
	printf("交换前：a = %d b = %d\n", a, b);
	Swap(&a, &b);
	printf("交换后：a = %d b = %d\n", a, b);
	return 0;
}

//上面的代码，就是传址调用
//可以让被调函数和主调函数之间建立真正的练习
//在被调函数内部可以修改主调函数中的变量，所以
//未来被调函数只是需要主调函数中的变量值实现计算，就可以使用传址调用